#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>

#include "cfg.h"

#define TUNEUP32(x)     do{(x)|=(x)>>1; (x)|=(x)>>2; (x)|=(x)>>4; (x)|=(x)>>8; (x)|=(x)>>16;}while(0) 
#define	CFG_CAP_DEF		128
#define	CFG_ENT_SIZE	(512-1)

static char * stag = "section";

//lower case
static char * strlwc(char * s)
{
	if (s==NULL) return NULL;

	char * l = s;
	while (*l){
		*l = (char)tolower((int)*l);
		l ++;
	}
	return s ;
}

//skip head blank
static char * strskp(char * s)
{
	if (s==NULL) return NULL ;

	while (isspace((int)*s) && *s) s ++;

	return s;
}

//chop tail blank
static char * strchop(char * s)
{
	if (s==NULL) return NULL ;

	char * last = s + strlen(s);

	while (last > s && isspace((int)*(last-1))) last --;

	*last = 0;

	return s ;
}

//strip both head and tail blank
static char * strstrip(char * s)
{
	if (s==NULL) return NULL ;

	while (isspace((int)*s) && *s) s ++;

	char * last = s + strlen(s);

	while (last > s && isspace((int)*(last-1))) last --;

	*last = 0;

	return s ;
}

static unsigned int dohash(const char * key) 
{
	unsigned int hash;
	int len;
	int i; 

	len = strlen(key); 
	for (hash=0, i=0 ; i<len ; i++) {
		hash += (unsigned)key[i];
		hash += (hash<<10);
		hash ^= (hash>>6);
	}
	hash += (hash <<3);
	hash ^= (hash >>11);
	hash += (hash <<15);
	return hash;
}

static int grow(CFGMAP * p)
{
	if(p->size < p->cap){
		return 1;
	}
	unsigned int newcap = p->size ? p->size == 0 : CFG_CAP_DEF;
	TUNEUP32(newcap);
	CFGITEM * newarray = (CFGITEM *)realloc(p->array, sizeof(struct CFGITEM) * newcap);
	if(newarray == NULL){
		return -1;
	}
	p->cap = newcap;
	p->array = newarray;
	return 0;
}

static int set(CFGMAP * p, const char * section, const char * key, const char * value)
{
	assert(section != NULL);
	char * nkey;
	if(key == NULL){//only section
		if(*section == '\0'){
			return -2;
		}
		nkey = (char *)malloc(strlen(section) + 2);
		sprintf(nkey, "%s:", section);
	}else{
		if(*key == '\0'){
			return -2;
		}
		nkey = (char *)malloc(strlen(section) + strlen(key) + 2);
		sprintf(nkey, "%s:%s", section, key);
	}
	char * nvalue = value ? strdup(value) : NULL;

	unsigned int hash = dohash(nkey);

	CFGITEM * c = p->array;
	while(c != p->array + p->size){
		assert(c->key != NULL);
		if(c->hash == hash && !strcmp(c->key, nkey)){
			free(c->value);
			c->value = nvalue;
			return 1;//dup value
		}
		c ++;
	}
	if(p->size == p->cap && grow(p) < 0){
		return -1;
	}
	c = p->array + p->size++;
	c->hash = hash;
	c->key = nkey;
	c->value = nvalue;
	return 0;
}

static int clear(CFGMAP * p){
	CFGITEM * c = p->array;
	while(c != p->array + p->size){
		assert(c->key != NULL);
		free(c->key);
		free(c->value);
		c->key = c->value = NULL;
		c ++;
	}
	p->size = 0;

	return 0;
}

int cfg_load(CFGMAP * p, const char * path)
{
	clear(p);

    char line[CFG_ENT_SIZE+1] = {0};
    char section[CFG_ENT_SIZE+1] = {0};
    char key[CFG_ENT_SIZE+1] = {0};
    char value[CFG_ENT_SIZE+1] = {0};

    char * pos;
    FILE * file = fopen(path, "r");
	if (file ==NULL) {
		return -1;
	}

	*section = 0;
	int i = 0 ;
	while (fgets(line, CFG_ENT_SIZE, file)!=NULL) {
		i++ ;
		pos = strskp(line); /* Skip leading spaces */
		if (*pos==';' || *pos=='#' || *pos==0){
			continue ; /* Comment or Blank */
		}
		if (sscanf(pos, "[%[^]]", section)==1) {
			/* Valid section name */
			set(p, strlwc(strstrip(section)), NULL, stag);
		} else if (sscanf (pos, "%[^=] = \"%[^\"]\"", key, value) == 2
				||  sscanf (pos, "%[^=] = '%[^\']'",   key, value) == 2
				||  sscanf (pos, "%[^=] = %[^;#]",     key, value) == 2) {
			/*
			 * sscanf cannot handle "" or '' as empty value,
			 * this is done here
			 */
			if (!strcmp(value, "\"\"") || !strcmp(value, "''")) {
				* value = '\0';
			}
			set(p, strlwc(strstrip(section)), strlwc(strchop(key)), strchop(value));
		}
	}
	fclose(file);
	return 0 ;
}

int cfg_dump(CFGMAP * p)
{
    char section[CFG_ENT_SIZE+1] = {0};
    char * sep;

	CFGITEM * c = p->array;
	while(c != p->array + p->size){
		assert(c->key != NULL);
		sep = strchr(c->key, ':');
		if(sep == NULL){
			return -1;
		}
		if(*(sep + 1) == '\0'){
			strncpy(section, c->key, sep - c->key);
			section[sep - c->key] = '\0';
			printf("[%s]\n", section);
		}else{
			printf("%s = %s\n", sep + 1, c->value);
		}
		c++;
	}
	return 0;
}

CFGMAP * cfg_new()
{
	CFGMAP * p = (CFGMAP * )malloc(sizeof(struct CFGMAP));
	p->size = 0;
	p->cap = 0;
	p->array = NULL;
	if(grow(p) < 0){
		free(p);
		return NULL;
	}
	return p;
}

int cfg_free(CFGMAP * p)
{
	clear(p);
	free(p->array);
	p->array = NULL;
	p->cap = 0;
	free(p);
	return 0;
}

static const char * get(CFGMAP * p, const char * key)
{
	unsigned int hash = dohash(key);
	CFGITEM * c = p->array;
	while(c != p->array + p->size){
		assert(c->key != NULL);
		if(c->hash == hash && !strcmp(c->key, key)){
			return c->value;
		}//else continue
		c ++;
	}
	return NULL;
}

const char * cfg_get_str(CFGMAP * p, const char * key, const char * def)
{
	const char * value = get(p, key);
	if(value == NULL){
		return def;
	}
	return value;
}

int cfg_get_int(CFGMAP * p, const char * key, int def)
{
	const char * value = get(p, key);
	if(value == NULL){
		return def;
	}
	return atoi(value);
}

double cfg_get_double(CFGMAP * p, const char * key, double def)
{
	const char * value = get(p, key);
	if(value == NULL){
		return def;
	}
	return atof(value);
}

int cfg_get_bool(CFGMAP * p, const char * key, int def)
{
	const char * value = get(p, key);
	if(value == NULL){
		return def;
	}
    if(*value=='y'||*value=='Y'||*value=='1'||*value=='t'||*value=='T'){
		return 1;
    }else if(*value=='n'||*value=='N'||*value=='0'||*value=='f'||*value=='F'){
		return 0;
    }
    return def;
}

int cfg_isset_section(CFGMAP * p, const char * section)
{
	char nkey[CFG_ENT_SIZE];
	sprintf(nkey, "%s:", section);
	const char * value = get(p, nkey);
	if(value != NULL){
		return 1;
	}
	return 0;
}


#ifdef CFG_MAIN
int main(int argc, char ** argv)
{
	CFGMAP * p = cfg_new();
	if(p == NULL){
		return 1;
	}
	if(cfg_load(p, "test.ini") < 0){
		cfg_free(p);
		return 2;
	}
	if(cfg_dump(p) < 0){
		cfg_free(p);
		return 3;
	}
	printf("int:%d\n", cfg_get_int(p, "values:int", 0));
	printf("double:%f\n", cfg_get_double(p, "values:real", 0));
	printf("string:%s\n", cfg_get_str(p, "values:str", "nofound"));
	printf("bool:%d\n", cfg_get_bool(p, "values:bool", 0));
	cfg_free(p);
	return 0;
}
#endif
